# -*- coding:utf-8 -*-

# @Time: 2025/3/11 23:50

# @Author: zhangbo

# @File: ObjectMap.py

# @version: v1.0

import time
from time import sleep

from selenium.common.exceptions import ElementNotVisibleException, WebDriverException, StaleElementReferenceException
from selenium.webdriver.common.keys import Keys
from utils.tools import get_project_name, sep



class ObjectMap:

    def wait_for_ready_state_complete(self, driver, timeout=30):
        """
        等待测试页面全部加载完成
        :param driver:
        :param timeout:
        :return:
        """

        # 开始时间
        start_time = time.time() * 1000
        # 结束时间
        end_time = start_time + (timeout * 1000)

        for i in range(int(timeout * 10)):
            try:
                # 获取页面的状态
                ready_state = driver.execute_script("return document.readyState")
            except WebDriverException:
                # 如果有driver的错误，就直接跳过（执行js语句失败，多半是driver的问题）
                time.sleep(0.03)
                return True
            # 判断是否加载完成
            if ready_state == "complete":
                time.sleep(0.01)
                return True
            else:
                now_time = time.time() * 1000
                if now_time >= end_time:
                    break
                else:
                    pass
        raise Exception("页面在规定时间内：%s秒，仍然没有加载完成" % timeout)

    def get_element(self, driver, location_type, location_value, timeout=10, must_be_visible=False):

        """
        定位元素
        :param driver:
        :param location_type:
        :param location_value:
        :param timeout:
        :param must_be_visible:
        :return:
        """

        # 开始时间
        start_time = time.time() * 1000

        # 结束时间
        end_time = start_time + (timeout * 1000)

        for i in range(int(timeout * 10)):

            try:
                # 查找元素
                element = driver.find_element(by=location_type, value=location_value)

                # 判断元素是否必须可见
                if not must_be_visible:
                    return element
                else:
                    # 元素是否是可见的
                    if element.is_displayed():
                        return element
                    else:
                        raise Exception()
            except:
                now_time = time.time() * 1000
                if now_time >= end_time:
                    break
                else:
                    pass
            time.sleep(0.1)
        raise ElementNotVisibleException("元素定位失败，定位方式：" + location_type + "定位表达式：" + location_value)

    def wait_element_displayed(self, driver, location_type, location_value, timeout=30):

        """
        等待元素消失
        :param driver:
        :param location_type:
        :param location_value:
        :param timeout:
        :return:
        """

        # 逻辑为定位方式不为空，就需要等待元素的消失或者出现
        if location_type:
            # 开始时间
            start_time = time.time() * 1000

            # 结束时间
            stop_time = start_time + (timeout * 1000)

            for i in range(int(timeout * 10)):
                try:
                    # 查找定位元素
                    element = driver.find_element(by=location_type, value=location_value)
                    # 判断元素是否是可见的
                    if element.is_displayed():

                        # 元素可见 判断是否超时
                        now_time = time.time() * 1000
                        if now_time >= stop_time:
                            break
                        else:
                            pass
                    else:
                        # 元素不可见 判断是否超时
                        now_time = time.time() * 1000
                        if now_time >= stop_time:
                            break
                        else:
                            pass
                        time.sleep(0.1)
                except Exception:
                    return True
            raise Exception(
                "元素在规定时间%s秒内没有消失" % timeout + "定位方式：" + location_type + "定位表达式" + location_value)
        # 否则直接结束
        else:
            pass

    def wait_element_appear(self, driver, location_type, location_value, timeout=30):

        """
        等待元素出现
        :param driver:
        :param location_type:
        :param location_value:
        :param timeout:
        :return:
        """

        if location_type:
            # 开始时间
            start_time = time.time() * 1000

            # 结束时间
            stop_time = start_time + (timeout * 1000)

            for i in range(int(timeout * 10)):
                try:
                    # 查找定位元素
                    element = driver.find_element(by=location_type, value=location_value)
                    if element.is_displayed():
                        return element
                    else:
                        now_time = time.time() * 1000
                        if now_time >= stop_time:
                            break
                        else:
                            pass
                except Exception:
                    now_time = time.time() * 1000
                    if now_time >= stop_time:
                        break
                    else:
                        pass
                    time.sleep(0.1)
            raise Exception(
                "元素在规定时间%s秒内没有出现" % timeout + "定位方式：" + location_type + "定位表达式" + location_value)
        else:
            pass

    def upload_file(self, driver, location_type, location_value, file_path):

        """
        文件上传的方法
        :param driver:
        :param location_type:
        :param location_value:
        :param file_path:
        :return:
        """
        element = self.get_element(driver, location_type, location_value)
        return element.send_keys(file_path)

    def switch_into_iframe(self, driver, location_iframe_type, location_iframe_value):

        """
        进入iframe
        :param driver:
        :param location_iframe_type:
        :param location_iframe_value:
        :return:
        """
        # 定位 iframe
        iframe_element = self.get_element(driver, location_iframe_type, location_iframe_value)

        # 切换到iframe
        driver.switch_to.frame(iframe_element)

    def switch_out_iframe(self, driver):

        """
        离开iframe，切回主文档
        :param driver:
        :return:
        """
        # 切回主文档
        driver.switch_to.parent_frame()

    def find_img_in_source(self, driver, img_name):

        """
        截图并在截图中查找目标文件
        :param driver:
        :param img_name:
        :return:
        """
        # 截图后图片保存的路径
        source_img_path = get_project_name() + sep(["static_source", "img", "source_img", img_name], add_sep_befor=True)

        # 需要查找的图片的路径
        search_img_path = get_project_name() + sep(["static_source", "img", "assert_img", img_name], add_sep_befor=True)

        # 截图并保存图片
        driver.get_screenshot_as_file(source_img_path)
        time.sleep(3)

        # 在原图中查找是否有指定的图片，返回信息值  比对值
        comparison_value = FindImg().get_confidence(source_img_path, search_img_path)

        return comparison_value

    def element_display(self, driver, location_type, location_value):
        """
        元素是否存在
        :param driver:
        :param location_type:
        :param location_value:
        :return:
        """

        element = self.wait_element_appear(driver=driver, location_type=location_type, location_value=location_value)

        if element:
            return True
        else:
            return False

    def element_click(self, driver, location_type, location_value, location_disappear_type=None,
                      location_disappear_value=None, location_appear_type=None, location_appear_value=None, timeout=10):

        """
        元素点击封装
        :param driver:
        :param location_type: 被点击元素的定位方式
        :param location_value: 被点击元素的定位值
        :param location_disappear_type: 点击后消失元素的定位方式
        :param location_disappear_value: 点击后消失元素的定位值
        :param location_appear_type: 点击后出现元素的定位方式
        :param location_appear_value: 点击后出现元素的定位值
        :param timeout:
        :return:
        """

        # 点击操作，元素必须要先出现
        element = self.wait_element_appear(driver,
                                           location_type=location_type,
                                           location_value=location_value,
                                           timeout=timeout,
                                           )
        try:

            # 点击元素
            element.click()
        except StaleElementReferenceException:
            self.wait_for_ready_state_complete(driver)
            sleep(0.06)
            element = self.wait_element_appear(driver,
                                               location_type=location_type,
                                               location_value=location_value,
                                               timeout=timeout,
                                               )
            element.click()
        except Exception as e:
            print("页面出现异常，元素不可点击", e)
            return False
        try:
            # 点击后，等待元素消失
            if location_disappear_type:
                self.wait_element_displayed(driver, location_disappear_type, location_disappear_value)

            # 点击后，等待元素出现
            if location_appear_type:
                self.wait_element_appear(driver, location_appear_type, location_appear_value)
        except Exception as e:
            print("点击元素后，等待元素消失或出现失败", e)

    def element_fill_value(self, driver, location_type, location_value, fill_value, timeout=15):
        """
        输入框填值操作
        :param driver: 浏览器驱动
        :param location_type: 定位方式
        :param location_value: 定位值
        :param fill_value: 填入的值
        :param timeout: 超时时间
        :return:
        """

        # 填值操作，元素必须先出现,调用等待元素出现方法
        element = self.wait_element_appear(driver,
                                           location_type=location_type,
                                           location_value=location_value,
                                           timeout=timeout,
                                           )
        # 异常捕获（如果元素还没加载完成，就对元素进行捕获，就会抛出 如下 异常）
        try:
            # 清空输入框中的值
            element.clear()

        except StaleElementReferenceException as e:
            self.wait_for_ready_state_complete(driver)
            time.sleep(0.06)
            element = self.wait_element_appear(driver,
                                               location_type=location_type,
                                               location_value=location_value,
                                               timeout=timeout,
                                               )
            try:
                element.clear()
            except Exception:
                pass
        except Exception:
            pass

        # 将填入的值转成字符串
        if type(fill_value) is not str:
            fill_value = str(fill_value)

        try:
            # 如果填入的值是以 \n 结尾的，那就认为输入后需要回车
            if fill_value.endswith("\n"):
                fill_value = fill_value[:-1]
                element.send_keys(fill_value)
                # 输入键盘回车
                element.send_keys(Keys.RETURN)
                # 等待页面元素加载完成
                self.wait_for_ready_state_complete(driver)
            else:
                element.send_keys(fill_value)
                self.wait_for_ready_state_complete(driver)
        except StaleElementReferenceException:
            self.wait_for_ready_state_complete(driver)
            time.sleep(0.06)
            element = self.wait_element_appear(driver=driver, location_type=location_type,
                                               location_value=location_value)
            element.clear()
            if not fill_value.endswith("\n"):
                element.send_keys(fill_value)
                self.wait_for_ready_state_complete(driver)
            else:
                fill_value = fill_value[:-1]
                element.send_keys(fill_value)
                element.send_keys(Keys.RETURN)
                self.wait_for_ready_state_complete(driver)
        except Exception:
            raise Exception("元素填值失败")

        return True

    def is_lcd_input_flag(self, driver, location_type, input_location_value, input_selected_value, timeout=30):
        """
        判断  报警项 是否选中
        :param driver: driver
        :param location_type: 定位方式
        :param input_location_value: input 复选框的定位值
        :param input_selected_value: span 复选框的定位值
        :return:
        """

        # 定位元素出现
        element = self.get_element(driver, location_type=location_type, location_value=input_location_value)

        try:
            # 判断元素是否被勾选
            if element.is_selected():
                return True
            else:
                # 等待复选框 前的span 标签出现
                element_span = self.wait_element_appear(driver,
                                                        location_type=location_type,
                                                        location_value=input_selected_value,
                                                        timeout=timeout,
                                                        )

                # 点击勾选
                element_span.click()
                if element.is_selected():
                    return True
                else:
                    return False
        except StaleElementReferenceException:
            self.wait_for_ready_state_complete(driver)
            time.sleep(0.06)
            # 定位元素出现
            element = self.get_element(driver, location_type=location_type, location_value=input_location_value)

            # 判断元素是否被勾选
            if element.is_selected():
                return True
            else:
                # 等待复选框 前的span 标签出现
                element_span = self.wait_element_appear(driver,
                                                        location_type=location_type,
                                                        location_value=input_selected_value,
                                                        timeout=timeout,
                                                        )

                # 点击勾选
                element_span.click()
                if element.is_selected():
                    return True
                else:
                    return False
